#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <fstream>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "log.h"

using namespace std;



struct mutex_semaforo
{
   pthread_mutex_t MUTEX ;
   pthread_cond_t COND ;
};

mutex_semaforo semaforo;
mutex_semaforo semaforo_buffer;

/* Variables compartidas */
char  command[1024];
char  parametro[1024];
char buffer[99999];

/* Variables globales generales*/
SSL *ssl;
char mensaje[1024];
string comandolimpio, parameter, comandos;
char * cadenaPrimitiva = (char *)malloc(sizeof(char)*1024);
int entroif;
size_t posicion, posicionanterior;
int PID;

/*Variables obtenidas del archivo de configuracion*/
int puertoSSL;
char ipServidor[16];


void *funcionConsola (void *parametro);
void *funcionComando (void *parametro);


int OpenConnection(int port);
SSL_CTX* InitCTX(void);
void procesoComandos (char* comando, char* parametro);
void *funcionLeerDeConsola (void *parametro);
void *funcionComandos (void *parametro);
void stat(char* parametro);
void body(char* parametro);
void group(char* parametro);
void listgroup(char* parametro);
void head(char* parametro);
void article(char* parametro);
void deprecatedhelp(void);
void listnewsgroups(void);
void help(void);
void stat (char* parametro);
void quit(void);
int obtenerDatosConfig(void);

int main() {
    SSL_CTX *ctx;
    int server, ret;
    size_t tamanioStack;
    pthread_attr_t attr,attr1;
    pthread_mutex_init(&semaforo.MUTEX,NULL);
    pthread_cond_init(&semaforo.COND,NULL);
    pthread_t thread1, thread2; /* Identificador de los thread's */

    
    tamanioStack = (PTHREAD_STACK_MIN + 1024 * 1024 );
    ret = pthread_attr_init(&attr);
    ret = pthread_attr_setstacksize (&attr, tamanioStack);
    ret = pthread_attr_init(&attr1);
    ret = pthread_attr_setstacksize (&attr1, tamanioStack);
    /*ret = pthread_attr_getstacksize (&attr, &tamanioStack);*/
    obtenerDatosConfig();

    PID=getpid(); /* Obtiene el ID del Proceso*/
    cadenaPrimitiva = (char *)malloc(sizeof(char)*1024);

    ctx = InitCTX();
    server = OpenConnection(puertoSSL);
    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, server);
    if ( SSL_connect(ssl) == -1 ) {
        ERR_print_errors_fp(stderr);
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo la conexion del socket SSL");
    } else {
        printf("Conectado! usando encriptado: %s\n", SSL_get_cipher(ssl));
        escribirLog("NNTPSSLClient",PID,"INFO: Se conecto al servidor usando SSL");
        pthread_create(&thread1,&attr,&funcionConsola,NULL);
        pthread_create(&thread2,&attr1,&funcionComando,NULL);
    }

    pthread_join(thread1, NULL);/*espera la finalizacion del los demas hilos*/
    pthread_join(thread2, NULL);/*espera la finalizacion del los demas hilos*/
    pthread_mutex_destroy(&semaforo.MUTEX);
    SSL_free(ssl);
    close(server);
    SSL_CTX_free(ctx);
}

void *funcionConsola (void *parametre){

    entroif = 0;
    cout << "Ingrese un comando: \n" << endl;
    cin.getline(cadenaPrimitiva,1024);
    comandos.assign(cadenaPrimitiva);
    memset(buffer, '\0', 99999);
    while(strcmp(cadenaPrimitiva,"quit") != 0){
        posicion = comandos.find_first_of(" ");
        comandolimpio = comandos.substr(0,posicion);
        if ( string::npos != posicion ) {
            parameter = comandos.substr(posicion+1);
        } else {
            parameter.clear();
        }
    /* Cargar el comando y el parametro  */
        strcpy (command, comandolimpio.c_str());
        strcpy (parametro, parameter.c_str());
        pthread_cond_signal(&semaforo.COND);
        pthread_cond_wait(&semaforo.COND,&semaforo.MUTEX);
        cout << buffer; /* Imprimo la respuesta del servidor */
        memset(buffer, '\0', 99999);
        cout << "Ingrese  otro comando: \n";
        cin.getline(cadenaPrimitiva,1024);
        comandos.assign(cadenaPrimitiva);
        entroif = 0;
    }
    strcpy(command,"quit");
    pthread_cond_signal(&semaforo.COND);
    pthread_cond_wait(&semaforo.COND,&semaforo.MUTEX);
    cout << buffer; /* muestro por pantalla la despedida del server*/
    pthread_exit(NULL);
}



void *funcionComando (void *parametre){

    pthread_cond_wait(&semaforo.COND,&semaforo.MUTEX);

    while(strcmp(command,"quit") != 0) {
        procesoComandos (command, parametro);
        pthread_cond_signal(&semaforo.COND);
        pthread_cond_wait(&semaforo.COND,&semaforo.MUTEX);
    }
    procesoComandos (command, parametro);
    pthread_mutex_unlock(&semaforo.MUTEX);
    pthread_cond_signal(&semaforo.COND);
    pthread_exit(NULL);
}

void procesoComandos (char* comando, char* parametro){

    if (strcmp(comando,"group") == 0){
        entroif=1;
        group(parametro);
    }

    if (strcmp(comando,"article") == 0){
        entroif=1;
        article(parametro);
    }

    if (strcmp(comando,"stat") == 0){
        entroif=1;
        stat(parametro);
    }

    if (strcmp(comando,"head") == 0 ){
        entroif=1;
        head(parametro);
    }

    if (strcmp(comando,"body") == 0){
        entroif=1;
        body(parametro);
    }

    if (strcmp(comando,"listgroup") == 0){
        entroif=1;
        listgroup(parametro);
    }

    if (strcmp(comando,"help") == 0){
        entroif=1;
        help();
    }

    if (strcmp(comando,"quit") == 0){
        entroif=1;
        quit();
    }

    if (strcmp(comando,"list") == 0 && strcmp(parametro,"newsgroups") == 0) {
        listnewsgroups();
        entroif=1;
    }

    if (entroif != 1){
    printf("Comando no valido\n");
    }
}

int OpenConnection(int port)
{
    int sd;
    struct sockaddr_in addr;

    sd = socket(PF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ipServidor); /*INADDR_ANY;*/
    if ( connect(sd,(struct sockaddr*) &addr, sizeof(addr)) != 0 ) {
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo la conexion del socket");
        close(sd);
        abort();
    }
    return sd;
}

SSL_CTX* InitCTX(void)
{
    SSL_METHOD *method;
    SSL_CTX *ctx;

    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    method = SSLv2_client_method();
    ctx = SSL_CTX_new(method);
    if ( ctx == NULL ) {
        ERR_print_errors_fp(stderr);
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo la inicializacion de contexto SSL");
        abort();
    }
    return ctx;
}



/* Tratamiento de los comandos */

void body(char* parametro) {
    strcpy(mensaje,"body ");
    strcat(mensaje,parametro);
    strcat(mensaje,"\r\n");

    if(SSL_write(ssl,mensaje,strlen(mensaje)) == strlen(mensaje)){
       escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio del comando body");
    }
    memset (mensaje, '\0',1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
        strcpy(buffer,mensaje);
    }
    if (mensaje[0] == '4') return ;
    while (strstr(mensaje,"\r\n.\r\n")== NULL){
        memset (mensaje, '\0',1024);
        if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
	    strcat(buffer,mensaje);
    	}
    }
}

void group(char* parametro) {
     /*En la fe de erratas no aparece este comando.Lo deje para hacer pruebas.*/
    /*strcpy(mensaje,"group free.virginmedia.discussion.general\r\n");*/
    strcpy(mensaje,"group ");
    strcat(mensaje,parametro);
    strcat(mensaje,"\r\n");
    if(SSL_write(ssl,mensaje,strlen(mensaje)) == strlen(mensaje)){
        cout << "se envio correctamente" << endl;
    }
    memset(mensaje,'\0',1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
	cout << mensaje;
    }
}

void listgroup(char *parametro) {
    strcpy(mensaje,"listgroup ");
    strcat(mensaje,parametro);
    strcat(mensaje,"\r\n");
    if(SSL_write(ssl,mensaje,strlen(mensaje)) != strlen(mensaje)){
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio de sockets listgroup");
    }
    memset(mensaje, '\0', 1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
    	strcpy(buffer,mensaje);
    }
    if (mensaje[0] == '4') return ;
    while (strstr(mensaje,"\r\n.\r\n")== NULL){
        memset(mensaje,'\0',1024);
        if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
            strcat(buffer,mensaje);
    	}
    }

}

void head(char* parametro) {

    strcpy(mensaje,"head ");
    strcat(mensaje,parametro);
    strcat(mensaje,"\r\n");

    if(SSL_write(ssl,mensaje,strlen(mensaje)) != strlen(mensaje)){
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio del comando head");
    }
    memset (mensaje, '\0',1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
        strcpy(buffer,mensaje);
    }
    if (mensaje[0] == '4') return ;
    while (strstr(mensaje,"\r\n.\r\n")== NULL){
        memset (mensaje, '\0',1024);
        if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
            strcat(buffer,mensaje);
    	}
    }
}


void article(char* comando) {

    strcpy(mensaje,"article ");
    strcat(mensaje,comando);
    strcat(mensaje,"\r\n");
    if(SSL_write(ssl,mensaje,strlen(mensaje)) != strlen(mensaje)){
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio del comando article");
    }
    memset (mensaje, '\0',1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
        strcpy(buffer,mensaje);
    }
    if (mensaje[0] == '4') return ;
    while (strstr(mensaje,"\r\n.\r\n")== NULL){
        memset (mensaje, '\0',1024);
        if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
            strcat(buffer,mensaje);
    	}
    }
}

void listnewsgroups() {

    strcpy(mensaje,"list newsgroups\r\n");
    if(SSL_write(ssl,mensaje,strlen(mensaje)) != strlen(mensaje)){
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio del comando list newsgroups");
    }
    memset (mensaje, '\0',1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
        strcpy(buffer,mensaje);
    }
    if (mensaje[0] == '4') return ;
    while (strstr(mensaje,"\r\n.\r\n")== NULL){
        memset (mensaje, '\0',1024);
        if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
	    strcat(buffer,mensaje);
    	}
    }
}

void stat(char* parametro) {

	strcpy(mensaje,"stat ");
	strcat(mensaje,parametro);
	strcat(mensaje,"\r\n");

	if(SSL_write(ssl,mensaje,strlen(mensaje) ) != strlen(mensaje)){
           escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio del comando stat");
    }
   	memset (mensaje, '\0',1024);
   	if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
	    strcpy(buffer,mensaje);
    }

}

void quit() {
    memset(mensaje,'\0',1024);
    strcpy(mensaje,"quit\r\n");
    if(SSL_write(ssl,mensaje,strlen(mensaje)) != strlen(mensaje)){
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio del comando quit");
    }
    memset(mensaje,'\0',1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
        strcpy(buffer,mensaje);
    }
}

void help() {
    memset(mensaje,'\0',1024);
    strcpy(mensaje,"help\r\n");
    if(SSL_write(ssl,mensaje,strlen(mensaje)) != strlen(mensaje)){
        escribirLog("NNTPSSLClient",PID,"ERROR: Fallo el envio del comando help");
    }
    memset(mensaje,'\0',1024);
    if(SSL_read(ssl,mensaje,1024*sizeof(char))>0){
        strcpy(buffer,mensaje);
    }
}


int obtenerDatosConfig(void){
	FILE* arch;
	char cad[100+2];
	char* paux;

	if ((arch=fopen("ClienteConfig.txt","rt")) == NULL) {
		printf("Error al abrir el archivo de configuracion. Se detiene la ejecucion\n");
		escribirLog("NNTPSSLClient",PID,"ERROR: No se pudo abrir el archivo de configuracion. Se detiene la ejecucion");
		return 1;
	}


	if (fgets(cad,100+2,arch) == NULL) {
		printf("El Archivo de Configuracion esta incompleto\n");
		return 1;
	}

	if ((paux=strtok(cad,":")) == NULL) {
		printf("El Archivo de Configuracion esta incompleto\n");
		return 1;
	}
	if ((paux=strtok(NULL, "\n\0")) == NULL){
		printf("El Archivo de Configuracion esta incompleto\n");
		return 1;
	}
	strcpy(ipServidor,paux);
	memset(cad,'\0',102);


	if (fgets(cad,100+2,arch) == NULL) {
		printf("El Archivo de Configuracion esta incompleto\n");
		return 1;
	}

	if ((paux=strtok(cad,":")) == NULL) {
		printf("El Archivo de Configuracion esta incompleto\n");
		return 1;
	}
	if ((paux=strtok(NULL,"\n\0")) == NULL) {
		printf("El Archivo de Configuracion esta incompleto\n");
		return 1;
	}
	puertoSSL=atoi(paux);
	fclose(arch);
	return 0;
}



/*
       __
w    c(..)o  (
 \ __ (-)   __)
       /\  (
      /(_)__)
      w /|
       | \
       m  m
*/

